// Copyright 2006 Mloofle Inc. All Rights Reserved.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * Represents an entire corpus of files.
 */
public class Corpus {

  public static Corpus createCorpus(RandomAccessFile input) throws IOException {
    // Jump to end; read header size; jump backwards to header
    final long indexFileSize = input.length();
    input.seek(indexFileSize - 4);
    int tailSize = input.readInt();
    input.seek(indexFileSize - tailSize);

    // 1: Date of time of construction
    final long indexCreationDate = input.readLong();

    // 2: Variable break characters
    String varBreakChars = "";
    for (byte b = input.readByte(); b != 0; b = input.readByte()) {
      varBreakChars += (char) (b & 0xFF);
    }
    final CharacterAnalyzer cAnalyzer = new CharacterAnalyzer(varBreakChars);

    // 3: stop word file
    String stopFileName = input.readUTF();
    final WordAnalyzer wAnalyzer;
    if (stopFileName.length() > 0) {
      InputStream stopStream = null;
      try {
        stopStream = FileStreamCreator.createInputStream(stopFileName);
        File stopFile = new File(stopFileName);
        if (stopFile.lastModified() > indexCreationDate) {
          Util.errExit("Stop file '" + stopFileName + "' has been modified");
        }
        wAnalyzer = new WordAnalyzer(cAnalyzer, stopStream);
      } catch (FileNotFoundException e) {
        Util.errExit("Stop file '" + stopFileName + "' does not exist");
        return null;
      } catch (IOException e) {
        Util.errExit("Stop file '" + stopFileName + "' could not be read from");
        return null;
      } finally {
        Util.close(stopStream);
      }
    } else {
      wAnalyzer = new WordAnalyzer();
    }

    // 4: information about each file
    LinkedList files = new LinkedList();
    int corpusBytes = 0, corpusWords = 0, corpusLines = 0;
    for (String fileName = input.readUTF(); fileName.length() > 0; fileName = input.readUTF()) {
      int bytes = input.readInt();
      int words = input.readInt();
      int lines = input.readInt();
      corpusBytes += bytes;
      corpusWords += words;
      corpusLines += lines;
      CorpusFile file = new CorpusFile(fileName, bytes, words, lines);
      files.addLast(file);
    }

    CorpusFile[] fileArray = (CorpusFile[]) files.toArray(new CorpusFile[files.size()]);

    // Initialize all files with bloom filter lines
    input.seek(0);
    BitReader reader = new BitReader(input);
    for (int i = 0; i < fileArray.length; ++i) {
      fileArray[i].initLines(reader);
    }

    return new Corpus(indexCreationDate, indexFileSize, varBreakChars,
        cAnalyzer, stopFileName, wAnalyzer, fileArray);
  }

  public Corpus(long indexCreationDate, long indexFileSize,
      String varBreakChars, CharacterAnalyzer cAnalyzer, String stopFileName,
      WordAnalyzer wAnalyzer, CorpusFile[] files) {
    this.indexCreationDate = indexCreationDate;
    this.indexFileSize = indexFileSize;
    this.varBreakChars = varBreakChars;
    this.cAnalyzer = cAnalyzer;
    this.stopFileName = stopFileName;
    this.wAnalyzer = wAnalyzer;
    this.files = files;
  }

  public CharacterAnalyzer getCharacterAnalyzer() {
    return cAnalyzer;
  }

  public long getIndexCreationDate() {
    return indexCreationDate;
  }

  public WordAnalyzer getWordAnalyzer() {
    return wAnalyzer;
  }

  public void printIndex() {
    System.out.println("AIM.out");

    // print date of time of construction
    Date indexDate = new Date(indexCreationDate);
    System.out.println("Created: " + indexDate);

    // print variable break characters
    System.out.println("Variable break characters=" + varBreakChars);

    // print stop word file name
    if (stopFileName.length() > 0) {
      System.out.println("Stop file: " + stopFileName);
    } else {
      System.out.println("No stop words");
    }

    // print index file size
    System.out.println("Index size: " + indexFileSize + " bytes");
  }

  public void printSizeRatio() {
    long corpusBytes = 0;
    for (int i = 0; i < files.length; ++i) {
      corpusBytes += files[i].getBytes();
    }
    System.out.println("Index size to Corpus size: " + indexFileSize + "/"
        + corpusBytes + " = " + (double) indexFileSize / corpusBytes);
  }

  public void printStats() {
    long corpusBytes = 0, corpusWords = 0, corpusLines = 0;
    for (int i = 0; i < files.length; ++i) {
      CorpusFile file = files[i];
      corpusBytes += file.getBytes();
      corpusWords += file.getWords();
      corpusLines += file.getLines();
      System.out.println(file.getFilePath() + ": " + file.getBytes()
          + " bytes, " + file.getWords() + " words, " + file.getLines()
          + " lines");
    }
    System.out.println("Corpus Total: " + corpusBytes + " bytes, "
        + corpusWords + " words, " + corpusLines + " lines, " + files.length
        + " files");
  }

  public ResultSet runQuery(Query query) {
    // PRE: query is non-null
    // PRE: query contains no stopwords
    // create a place to store results
    List resultList = new ArrayList();
    // find possible matches
    for (int i = 0; i < files.length; ++i) {
      files[i].runQuery(query, resultList);
    }

    // Transform list into array
    PossibleMatch[] resultArray = (PossibleMatch[]) resultList.toArray(new PossibleMatch[resultList.size()]);
    // Construct resultset
    return new ResultSet(resultArray);
  }

  private final CharacterAnalyzer cAnalyzer;
  private final CorpusFile[] files;
  private final long indexCreationDate;
  private final long indexFileSize;
  private final String stopFileName;
  private final String varBreakChars;
  private final WordAnalyzer wAnalyzer;

}
